使用Phaser来开发我的炉石传说(三 最终篇)

4.战斗逻辑

战斗逻辑可以说是我们游戏核心中的核心了。实现起来也不是很复杂,这里先交待一下思路。

战斗,无非就是战场上随从之间和玩家英雄之前的属性数据的一些结算而已。我的鳄鱼攻击你的鱼人宝宝,那么我的鳄鱼生命值就减去你的鱼人宝宝随从的攻击力,如果为负,说明鳄鱼人宝宝的攻击力大于我们的鳄鱼随从的攻击力,那么鳄鱼就会阵亡。鱼人宝宝同样要做相同的结算,如果鱼人宝宝的生命值大于鳄鱼的攻击力,那么承受这次攻击之后,鱼人宝宝并不会阵亡,但要将生命值进行更新,也就是鱼人宝宝的最新生命值应该是之前生命值减去鳄鱼的攻击力才对。如果攻击的目标是英雄,直接用英雄的血量去减去攻击者的攻击力。当英雄的血量降到1点以下时(不包含1点),那么英雄也就阵亡,游戏的胜负也就分出来了。

ok,了解规则之后,我们看看当前游戏中的战斗逻辑的触发点。

  • 1、当我们攻击敌方的战场随从时,会触发战斗结算
  • 2、当我们攻击敌方英雄本体时,会触发战斗结算
  • 3、当敌人攻击我们的战场随从时,会触发战斗结算
  • 4、当敌人攻击我们的英雄本体时,会触发战斗结算

ok,弄明白这四种结算触发点之后,我们就可以开始来写逻辑了。

首先要做的第一件事,就是要把战场上随从的状态表示出来。战场上的随从有两种状态,休眠状态和激活状态。被召唤上场的随从第一回合是无法进行攻击的,需要用休眠状态来表示,在第二个回合,上一回合被召唤出来处于休眠状态的随从就可以发动攻击了,在他攻击完之后,又用进入休眠状态,直到下一个回合解除休眠。

来看下我们对Fighter.js类的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

/**
* 战斗元素类
* @param game
* @param x [number] 初始化的
*/

function Fighter(game) {
this.fightObj = []; // 战斗随从数组
this.x = 150;
this.y = game.world.centerY + 30;
}

Fighter.prototype.init = function (game) {
}

// 生成战斗随从
Fighter.prototype.buildFighter = function (game, hp, attack, cnName, picName) {
var fightBg = game.add.image(this.x, this.y, picName);

fightBg.hp = hp;
fightBg.attack = attack;
fightBg.cnName = cnName;
fightBg.picName = picName;
fightBg.sleep = true; // 休眠状态,在出牌的第一回合无法进行攻击
var _style = {
fill: "#fff",
fontSize: "12pt"
}
// 设置生命值
var hp_text = game.add.text(75, 105, hp, _style);
hp_text.anchor.set(0.5);
hp_text.key = "hp";

// 设置
var attack_text = game.add.text(17, 105, attack, _style);
attack_text.anchor.set(0.5);
attack_text.key = "attack";

var attack_tag = game.add.image(48, -15, "attack_icon");
attack_tag.key = "attack_tag";
attack_tag.scale.set(0.5);
attack_tag.anchor.set(0.5);
attack_tag.alpha = 0;

fightBg.addChild(attack_text);
fightBg.addChild(hp_text);
fightBg.addChild(attack_tag);
fightBg.alpha = 0.7; // sleep状态无法攻击
this.fightObj.push(fightBg);
this.reListObjs();

}

Fighter.prototype.reListObjs = function () {
if (this.fightObj.length == 0) {
// 如果随从的队列为空,不进行排序
return;
} else {
// 重排战斗随从的数组
for (var i = 0; i < this.fightObj.length; i++) {
this.fightObj[i].x = this.x + i * 95;
}
}
}

Fighter.prototype.awakeFighter = function () {
if (this.fightObj.length == 0) {
return;
}
else {
for (var i = 0; i < this.fightObj.length; i++) {
this.fightObj[i].sleep = false; // 解除睡眠状态
this.fightObj[i].alpha = 1; // 解除睡眠状态后的view

}
}
}

module.exports = Fighter;

这里我们在buildFighter函数中新加了一个attack_tag图片,并且暂时将其隐藏了起来。这个图标是为了我们后面选择战斗随从而作标记的。并且我们将fightBg的默认alpha设为了0.7,这样,当我们的随从被第一次召唤出来的时候,就会以半透明显示了。只有当随从进入激活状态,也就是半透明不为1时,才能进行相关操作。

后面的awakeFighter是在每个回合开始之前唤醒我们的随从的方法。

最后在UIManager中激活我们的睡眠随从。

UIManager.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// 回合结束
UIManager.prototype.setTurnOverButton = function (game) {
var button = game.add.image(game.world.width - 150, game.world.centerY - 30, "hero_turn_button");
button.inputEnabled = true;
button.events.onInputDown.add(function () {
if (DataManager.turn == 0) {
button.loadTexture("enemy_turn_button");
DataManager.turn = 1;
}
if (DataManager.enemyFighters) {
DataManager.enemyFighters.awakeFighter(); // 解除敌人随从睡眠状态
}
var time = setTimeout(function () {
DataManager.AI.shotCard(game);
DataManager.heroFighters.awakeFighter(); // 解除玩家随从睡眠状态
clearTimeout(time);
}, 1000);

});
return button;
}

写到做到这里,我们开始要写战斗随从之间的战斗了。首先要把我方的随从激活,我们选择了哪个随从,那么拿到该随从的资料,然后再选择敌人的随从,两者作数据的相关结算。

看下我们Fighter.js类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

/**
* 战斗元素类
* @param game
* @param x [number] 初始化的
*/

var DataManager = require("./DataManager");

function Fighter(game) {
this.fightObj = []; // 战斗随从数组
this.x = 150;
this.y = game.world.centerY + 30;
}

Fighter.prototype.init = function (game) {
}

// 生成战斗随从
Fighter.prototype.buildFighter = function (game, hp, attack, cnName, picName) {
var fightBg = game.add.image(this.x, this.y, picName);

fightBg.hp = hp;
fightBg.attack = attack;
fightBg.cnName = cnName;
fightBg.picName = picName;
fightBg.sleep = true; // 休眠状态,在出牌的第一回合无法进行攻击
var _style = {
fill: "#fff",
fontSize: "12pt"
}
// 设置生命值
var hp_text = game.add.text(75, 105, hp, _style);
hp_text.anchor.set(0.5);
hp_text.key = "hp";

// 设置
var attack_text = game.add.text(17, 105, attack, _style);
attack_text.anchor.set(0.5);
attack_text.key = "attack";

var attack_tag = game.add.image(48, -15, "attack_icon");
attack_tag.key = "attack_tag";
attack_tag.scale.set(0.5);
attack_tag.anchor.set(0.5);
attack_tag.alpha = 0;

fightBg.addChild(attack_text);
fightBg.addChild(hp_text);
fightBg.addChild(attack_tag);
fightBg.alpha = 0.7; // sleep状态无法攻击
this.fightObj.push(fightBg);
this.reListObjs();

fightBg.inputEnabled = true;
fightBg.events.onInputDown.add(function () {
this.choiceFighter(fightBg);
}, this);

}

Fighter.prototype.reListObjs = function () {
if (this.fightObj.length == 0) {
// 如果随从的队列为空,不进行排序
return;
} else {
var _temp= [];

for(var j = 0; j<this.fightObj.length;j++){
if(this.fightObj[j].alive == true){
_temp.push(this.fightObj[j]);
}
}

this.fightObj = _temp;

// 重排战斗随从的数组
for (var i = 0; i < this.fightObj.length; i++) {
this.fightObj[i].x = this.x + i * 95;
}
}
}

Fighter.prototype.awakeFighter = function () {
if (this.fightObj.length == 0) {
return;
}
else {
for (var i = 0; i < this.fightObj.length; i++) {
this.fightObj[i].sleep = false; // 解除睡眠状态
this.fightObj[i].alpha = 1; // 解除睡眠状态后的view

}
}
}

Fighter.prototype.choiceFighter = function (fightBg) {
if (fightBg.sleep == true) {
alert("本回合无法操作该随从!");
}

else {
for (var i = 0; i < this.fightObj.length; i++) {
this.fightObj[i].children[2].alpha = 0;
}
fightBg.children[2].alpha = 1;
DataManager.heroFighterChoise = fightBg;
}
}

module.exports = Fighter;

这里我们引入了DataManager.js,因为我们的很多的数据处理需要在这个对象中进行。

1
heroFighterChoise:null, // 战斗随从的选择

同时,也在DataManager中加入这个玩家随从选择的管理对象。

由于我们是无法操纵敌人的战场随从的,而且在点击敌人的随从之后所触发的是战斗的结算。这里我们就需要在EnemyFighter.js类中,去重写choiceFighter方法。

打开EnemyFighter.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

// 重写choiseFighter
// 在玩家选择敌方随从时进行战斗结算
EnemyFighter.prototype.choiceFighter = function (fightBg) {
if (DataManager.heroFighterChoise == null) {
return;
}
else {

alert("我方的" + DataManager.heroFighterChoise.cnName + "攻击了敌人的" + fightBg.cnName);

var _heroFightHP = DataManager.heroFighterChoise.hp - fightBg.attack;
var _enemyFightHP = fightBg.hp - DataManager.heroFighterChoise.attack;

console.log(_heroFightHP,_enemyFightHP);

// 更新玩家的随从的hp
DataManager.heroFighterChoise.hp = _heroFightHP;
DataManager.heroFighterChoise.alpha = 0.7;
DataManager.heroFighterChoise.sleep = true;
DataManager.heroFighterChoise.children[2].alpha = 0;
DataManager.heroFighterChoise.children[1].setText(_heroFightHP);

// 更新敌人的玩家的hp
fightBg.hp = _enemyFightHP;
fightBg.children[1].setText(_enemyFightHP);

if (_heroFightHP <= 0) {
DataManager.heroFighterChoise.destroy();
DataManager.heroFighters.reListObjs();
}

if (_enemyFightHP <= 0) {
fightBg.destroy();
DataManager.enemyFighters.reListObjs();
}

DataManager.heroFighterChoise = null;

}
}

做完随从之间的战斗逻辑,还有一个对英雄造成伤害的逻辑结算。

打开我们的EnemyHead.js类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

/**
* 敌人头像
*/

var utils = require("../Utils");
var Head = require("./Head");
var DataManager = require("./DataManager");

function EnemyHead(game, textureName, positionX, positionY) {
Head.apply(this, arguments);
}


// 设置敌人头像
// 重写setPic
Head.prototype.setPic = function (game) {
var pic = game.add.image(0, 0, this.textureName);

pic.inputEnabled = true;
pic.events.onInputDown.add(function () {
if (DataManager.heroFighterChoise == null) {
return;
}

else {
var _hp = parseInt(this.HPObj.text) - DataManager.heroFighterChoise.attack;
this.HPObj.setText(_hp);

DataManager.heroFighterChoise.alpha = 0.7;
DataManager.heroFighterChoise.sleep = true;
DataManager.heroFighterChoise.children[2].alpha = 0;

alert("我方的"+DataManager.heroFighterChoise.cnName+"攻击了敌人英雄");

DataManager.heroFighterChoise = null;
console.log(123);
}

}, this);

return pic;
}

utils.extend(EnemyHead, Head);

module.exports = EnemyHead;

image

如图,基本的玩家战斗逻辑就搞定了。不过接下来我们还有几件事情要做。

  • 费用的管理
  • AI的随从操作
  • 补牌逻辑
  • 剩余卡牌的展示
  • 其他的完善工作

5.水晶的管理

前面我们已经完成了基本的一些战斗逻辑,但是如果玩家可以一次性把所有的手牌打光,这明显是不合理的。在炉石的游戏中,每个随从都是和水晶费用相关联的,而之前我们已经声明好了Fee.js这个类及其子类,接下来我们要使他发挥其真正的作用。

打开我们的Fee.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
* 费用管理类
*/

function Fee(game, x, y) {
this.feeObj = null;
this.x = x || game.world.width - 30;
this.y = y || 0;
this.init(game);
}

Fee.prototype.init = function (game) {
this.feeObj = this.setFeePic(game);
}

// 设置Fee背景以及文字
Fee.prototype.setFeePic = function (game) {
var fee = game.add.image(this.x, this.y, "fee");
var text = game.add.text(60, 28, "1/1", { fill: "#fff", fontSize: "18pt" });
text.anchor.set(0.5);
fee.addChild(text);
return text;
}

module.exports = Fee;

这里我们所做的改动不多,只是将初始化的显示数值由之前的”9/9”改成了”1/1”。打开DataManager.js,我们要加几个数据字段进去:

1
2
3

heroCurrentFee: 1, // 玩家当前费用
enemyCurrentFee: 1, // 敌人当前费用

打开UIManager.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

/**
* UI界面管理
*/

var BackGround = require("./BackGround");
var HeroHead = require("./HeroHead");
var EnemyHead = require("./EnemyHead");
var DataManager = require("./DataManager");
var HeroHandCard = require("./HeroHandCard");
var EnemyHandCard = require("./EnemyHandCard");
var HeroFee = require("./HeroFee");
var EnemyFee = require("./EnemyFee");
var AI = require("./AI");

var HeroFighter = require("./HeroFighter");

function UIManager(game) {
this.backgroundObj = null; // 背景图
this.turnOverButton = null; // 回合结束
this.shotCardButton = null; // 出牌按钮
this.init(game);
}

UIManager.prototype.init = function(game) {
this.backgroundObj = this.setBackGround(game); // 生成背景图

DataManager.heroHead = new HeroHead(game, "fighter_hero", 0, game.world.height - 140); // 生成玩家英雄头像
DataManager.enemyHead = new EnemyHead(game, "fighter_hero", 0, 0); // 生成电脑英雄头像

DataManager.turnOverButton = this.setTurnOverButton(game); // 设置回合结束按钮

DataManager.enemyHandCard = new EnemyHandCard(game); // 设置敌人手牌
console.log(DataManager.enemyHandCard);
DataManager.heroHandCard = new HeroHandCard(game, null, game.world.height - 120); // 设置玩家手牌

this.shotCardButton = this.setShotCardButton(game); // 设置出牌按钮

DataManager.heroFee = new HeroFee(game, game.world.width - 110, game.world.centerY + 42); // 英雄费用管理
DataManager.enemyFee = new EnemyFee(game, game.world.width - 110, game.world.centerY - 90); // 敌人费用管理

DataManager.AI = new AI(); // 创建AI
}

// 设置背景
UIManager.prototype.setBackGround = function(game) {
var background = new BackGround(game);
return background;
}

// 回合结束
UIManager.prototype.setTurnOverButton = function(game) {
var button = game.add.image(game.world.width - 150, game.world.centerY - 30, "hero_turn_button");
button.inputEnabled = true;
button.events.onInputDown.add(function() {
if (DataManager.turn == 0) {
button.loadTexture("enemy_turn_button");
DataManager.turn = 1;
}
if (DataManager.enemyFighters) {
DataManager.enemyFighters.awakeFighter(); // 解除敌人随从睡眠状态
}

DataManager.enemyFee.feeObj.setText(DataManager.fee + "/" + DataManager.fee);

var time = setTimeout(function() {
DataManager.AI.shotCard(game);
if (DataManager.heroFighters) {
DataManager.heroFighters.awakeFighter(); // 解除玩家随从睡眠状态
}

// 更新玩家费用的情况
DataManager.fee += 1;
DataManager.heroCurrentFee = DataManager.fee;
DataManager.heroFee.feeObj.setText(DataManager.fee + "/" + DataManager.fee);
clearTimeout(time);
}, 1000);

});
return button;
}

// 出牌按钮
UIManager.prototype.setShotCardButton = function(game) {
var shot = game.add.image(80, game.world.centerY - 10, "shot_card");
shot.anchor.set(0.5);
shot.inputEnabled = true;
shot.events.onInputDown.add(function() {
if (DataManager.turn != 0) {
return;
}

console.log("我出牌了");
if (DataManager.heroChoiseCard) {

// 检查选择卡牌的费用是否超出当前可用费用
if (DataManager.heroCurrentFee < DataManager.heroChoiseCard.cardInfo.fee) {
alert("你的费用不足,无法使用这张卡牌");
return;
}

DataManager.heroCurrentFee = DataManager.heroCurrentFee - DataManager.heroChoiseCard.cardInfo.fee;
DataManager.heroFee.feeObj.setText(DataManager.heroCurrentFee + "/" + DataManager.fee);

if (DataManager.heroFighters == null) {
DataManager.heroFighters = new HeroFighter(game);
DataManager.heroFighters.buildFighter(game, DataManager.heroChoiseCard.cardInfo.HP, DataManager.heroChoiseCard.cardInfo.attack, DataManager.heroChoiseCard.cardInfo.cnName, DataManager.heroChoiseCard.cardInfo.fight);
} else {
DataManager.heroFighters.buildFighter(game, DataManager.heroChoiseCard.cardInfo.HP, DataManager.heroChoiseCard.cardInfo.attack, DataManager.heroChoiseCard.cardInfo.cnName, DataManager.heroChoiseCard.cardInfo.fight);
}

DataManager.heroChoiseCard.destroy();
DataManager.heroHandCard.reListHandCard();
DataManager.heroChoiseCard = null;
}

});
return shot;
}
module.exports = UIManager;

水晶的逻辑主要是在出牌按钮之中,只有符合要求的卡牌才能被成功打出,水晶的更新主要是在回合结束的按钮之中完成,当敌人回合结束之后,玩家的水晶会被重置并上限加一,玩家的回合结束,那么敌人的水晶也会执行同样的逻辑。

水晶的控制也算是完成了。但是到这里我们发现,我们的电脑AI虽然会出牌了,随从却并没有采取果任何行动,像个木头人一样,这怎么行,这样的对战也是太无趣了。这里我们来开始写一点简单的AI进攻的逻辑。

打开AI.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

/**
* 电脑AI
*/
var DataManager = require("./DataManager");
var EnemyFighter = require("./EnemyFighter");

function AI() {
this.enemyChoise = null;
}

// 出牌
AI.prototype.shotCard = function (game) {
this.enemyChoise = this.choiseCard();
DataManager.turn = 0;

if (!this.enemyChoise) {
// 没有合适的卡牌
DataManager.turnOverButton.loadTexture("hero_turn_button");
alert("敌人选择不出牌,不知道有什么阴谋诡计");
return;
}

if (DataManager.enemyFighters == null) {
DataManager.enemyFighters = new EnemyFighter(game);
DataManager.enemyFighters.buildFighter(game, this.enemyChoise.cardInfo.HP, this.enemyChoise.cardInfo.attack, this.enemyChoise.cardInfo.cnName, this.enemyChoise.cardInfo.fight);
} else {
DataManager.enemyFighters.buildFighter(game, this.enemyChoise.cardInfo.HP, this.enemyChoise.cardInfo.attack, this.enemyChoise.cardInfo.cnName, this.enemyChoise.cardInfo.fight);
}

this.enemyChoise.destroy();
DataManager.enemyHandCard.reListHandCard();
this.enemyChoise = null;

DataManager.turnOverButton.loadTexture("hero_turn_button");
}

// 选择手牌
AI.prototype.choiseCard = function () {
var shotList = [];
var _fee = parseInt(DataManager.enemyFee.feeObj.text.split("/")[0]);

for (var i = 0; i < DataManager.enemyHandCard.cardViewList.length; i++) {
if (_fee >= DataManager.enemyHandCard.cardViewList[i].cardInfo.fee) {
// 只要费用允许,就放入可出的牌之中
shotList.push(DataManager.enemyHandCard.cardViewList[i]);
}
}

if (shotList.length >= 1) {
console.log(shotList);
// 返回左手第一张牌
return shotList[0];
}
}

// 选择要攻击的目标
AI.prototype.choiseAttackTarget = function () {
// 敌人没有随从
if (!DataManager.enemyFighters || DataManager.enemyFighters.fightObj.length == 0) {
return;
}
console.log(DataManager.enemyFighters);
if (!DataManager.heroFighters) { // 判断玩家的随从是否存在
for (var i = 0; i < DataManager.enemyFighters.fightObj.length; i++) {
if (DataManager.enemyFighters.fightObj[i].sleep == false) {
alert("敌人的" + DataManager.enemyFighters.fightObj[i].cnName + "攻击了你的英雄");

// 更新攻击之后的状态
DataManager.enemyFighters.fightObj[i].sleep = true;
DataManager.enemyFighters.fightObj[i].alpha = 0.7;
DataManager.heroHead.HPObj.setText(parseInt(DataManager.heroHead.HPObj.text) - DataManager.enemyFighters.fightObj[i].attack);
}
}
} else {

var _heroFightersAttack = 0;
var _enemyFightersAttack = 0;

// 计算电脑AI的场攻
for (var j = 0; j < DataManager.enemyFighters.fightObj.length; j++) {
_enemyFightersAttack += DataManager.enemyFighters.fightObj[j].attack;
}

// 计算玩家战场上的场攻
for (var k = 0; k < DataManager.heroFighters.fightObj.length; k++) {
_heroFightersAttack += DataManager.heroFighters.fightObj[k].attack;
}
var _destroyList = [];
for (var i = 0; i < DataManager.enemyFighters.fightObj.length; i++) {
if (DataManager.enemyFighters.fightObj[i].sleep == false) {
console.log("attack");

if (_enemyFightersAttack >= _heroFightersAttack) { // AI场攻大于玩家随从的场攻
alert("敌人的" + DataManager.enemyFighters.fightObj[i].cnName + "攻击了你的英雄");
// 更新攻击之后的状态
DataManager.enemyFighters.fightObj[i].sleep = true;
DataManager.enemyFighters.fightObj[i].alpha = 0.7;
DataManager.heroHead.HPObj.setText(parseInt(DataManager.heroHead.HPObj.text) - DataManager.enemyFighters.fightObj[i].attack);
} else {
// AI场攻小于玩家场攻则攻击随从
alert("敌方的" + DataManager.enemyFighters.fightObj[i].cnName + "攻击了我方的" + DataManager.heroFighters.fightObj[0].cnName);

var _heroFightHP = DataManager.enemyFighters.fightObj[i].hp - DataManager.heroFighters.fightObj[0].attack;
var _enemyFightHP = DataManager.heroFighters.fightObj[0].hp - DataManager.enemyFighters.fightObj[i].attack;

// 更新玩家的随从的hp
DataManager.enemyFighters.fightObj[i].hp = _heroFightHP;
DataManager.enemyFighters.fightObj[i].alpha = 0.7;
DataManager.enemyFighters.fightObj[i].sleep = true;
DataManager.enemyFighters.fightObj[i].children[2].alpha = 0;
DataManager.enemyFighters.fightObj[i].children[1].setText(_heroFightHP);

// 更新敌人的玩家的hp
DataManager.heroFighters.fightObj[0].hp = _enemyFightHP;
DataManager.heroFighters.fightObj[0].children[1].setText(_enemyFightHP);

if (_heroFightHP <= 0) {
_destroyList.push(DataManager.enemyFighters.fightObj[i]);
}

if (_enemyFightHP <= 0) {
DataManager.heroFighters.fightObj[0].destroy();
DataManager.heroFighters.reListObjs();
}
}
}
}

for (var n = 0; n < _destroyList.length; n++) {
_destroyList[n].destroy();
}
DataManager.enemyFighters.reListObjs();
}
}


module.exports = AI;

这里的AI逻辑很简单,首先对场上的随从的场攻进行一个判断,如果AI方的场攻大于玩家方的,那么AI就可以肆无忌惮的攻击玩家的英雄本体了,如果小于,则需要攻击玩家的随从来降低场攻,不过对随从的选择还是比较简单的,只是选择了战场随从的第一个来作为目标。如果你希望AI看起来更加聪明一点可以计算下玩家场上攻击力最高的随从,如果最高的攻击力随从死掉之后,再计算下双方的场攻,然后重复上面的逻辑。

最后放上玩家的补牌逻辑和电脑的补牌逻辑,玩家:打开HandCard.js加入addCard这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

// 回合开始时的补牌逻辑
HandCard.prototype.addCard = function (game) {
var _cardList = this.cardIDList.splice(0, 1);
console.log(_cardList);
for (var j = 0; j < CardConfig.card_info.length; j++) {
if (_cardList[0] == CardConfig.card_info[j].id) {
var card = game.add.image(this.x + (this.cardObjList.length) * 75, this.y, CardConfig.card_info[j].name);
card.cardInfo = {};
card.cardInfo.HP = CardConfig.card_info[j].hp; // 血量
card.cardInfo.attack = CardConfig.card_info[j].attack; // 攻击力
card.cardInfo.cnName = CardConfig.card_info[j].cn_name; // 中文名称
card.cardInfo.fee = CardConfig.card_info[j].fee; // 召唤费用
card.cardInfo.fight = CardConfig.card_info[j].fight; // 战斗图片
card.scale.set(0.5);

this.cardObjList.push(card);

card.inputEnabled = true;
card.events.onInputDown.add(function () {
this.inputEnabled = false; // 禁止玩家不停点击
if (DataManager.heroChoiseCard == null) {
DataManager.heroChoiseCard = this;
} else {
// 注册动画事件
var tween = game.add.tween(DataManager.heroChoiseCard).to({ y: DataManager.heroChoiseCard.y + 20 }, 200, "Linear", true);
DataManager.heroChoiseCard.inputEnabled = true;
// 执行动画
tween.start();
DataManager.heroChoiseCard = this;
}

var tween = game.add.tween(this).to({ y: this.y - 20 }, 200, "Linear", true);
tween.start();
tween.onComplete.add(function () {
});
}, card);
}
}

然后电脑的AI的补牌逻辑我们写在EnemyHandCard类之中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

/**
* 敌人的手牌类
*/

var HandCarnd = require("./HandCard");
var utils = require("../Utils");
var CardConfig = require("../config/CardConfig");

function EnemyHandCard(game) {
HandCarnd.apply(this, arguments);
// this.setRealHandCard(game); // 真实卡面
this.buildHandCardViewList(game); // 设置卡背

}

utils.extend(EnemyHandCard, HandCarnd);

// @override
// 重写relistHandCard方法
EnemyHandCard.prototype.reListHandCard = function () {
var self = this;
var _temp = [];
console.log(self.cardViewList);

if (self.cardViewList.length == 0) { // 没有手牌的情况
return;
} else {
for (var i = 0; i < self.cardViewList.length; i++) {
if (self.cardViewList[i].alive == true) { // 清除掉已经销毁了的手牌
_temp.push(self.cardViewList[i]);
}
}
self.cardViewList = _temp;

for (var j = 0; j < self.cardViewList.length; j++) { // 重新对手牌排序
self.cardViewList[j].x = self.x + j * 70;
}
}
}

// @override
// 重写回合开始时的补牌逻辑
EnemyHandCard.prototype.addCard = function (game) {
var _cardList = this.cardIDList.splice(0, 1);
console.log(_cardList);
for (var j = 0; j < CardConfig.card_info.length; j++) {

if (_cardList[0] == CardConfig.card_info[j].id) {
var card = game.add.image(this.x + this.cardViewList.length * 70, this.y, "card_back");

// 设置相应的数据
card.cardInfo = {};
card.cardInfo.HP = CardConfig.card_info[j].hp; // 血量
card.cardInfo.attack = CardConfig.card_info[j].attack; // 攻击力
card.cardInfo.cnName = CardConfig.card_info[j].cn_name; // 中文名称
card.cardInfo.fee = CardConfig.card_info[j].fee; // 召唤费用
card.cardInfo.fight = CardConfig.card_info[j].fight; // 战斗图片
card.scale.set(0.5);
this.cardViewList.push(card);
}
}
}

module.exports = EnemyHandCard;

最后记得在UIManager中去执行这两个方法,让玩家在出牌之前记得先补充一张手牌:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

// 回合结束
UIManager.prototype.setTurnOverButton = function (game) {
var button = game.add.image(game.world.width - 150, game.world.centerY - 30, "hero_turn_button");
button.inputEnabled = true;
button.events.onInputDown.add(function () {
if (DataManager.turn == 0) {
button.loadTexture("enemy_turn_button");
DataManager.turn = 1;
}
if (DataManager.enemyFighters) {
DataManager.enemyFighters.awakeFighter(); // 解除敌人随从睡眠状态
}

DataManager.enemyFee.feeObj.setText(DataManager.fee + "/" + DataManager.fee);
DataManager.enemyHandCard.addCard(game); // 敌人摸牌
var time = setTimeout(function () {
DataManager.AI.shotCard(game);
DataManager.AI.choiseAttackTarget(); // 电脑AI展开攻击
if (DataManager.heroFighters) {
DataManager.heroFighters.awakeFighter(); // 解除玩家随从睡眠状态
}

// 更新玩家费用的情况
DataManager.fee += 1;
DataManager.heroCurrentFee = DataManager.fee;
DataManager.heroFee.feeObj.setText(DataManager.fee + "/" + DataManager.fee);
DataManager.heroHandCard.addCard(game); // 玩家摸牌
clearTimeout(time);
}, 1000);

});
return button;
}

到这里,游戏的主要逻辑就走完了。不过游戏并没有完成。我们还有一些收尾工作要做。

比如我们要限制场上的随从的数量,要限制手牌的数量,牌库里的卡牌用完后的处理,还有当玩家或电脑AI的生命值降为0时游戏结束的判定。

先看下对场上随从数量的限制,打开UIManager.js,在其中的setShotCardButton方法中加入:

1
2
3
4
5
6
7
8
9
10

// 控制玩家场上的随从
try {
if (DataManager.heroFighters.fightObj.length >= 5) {
alert("您场上的随从已经到达了上限");
return;
}
} catch (e) {

}

同样的,在AI.js类中也找到AI的出牌方法shotCard,加上:

1
2
3
4
5
6
7
8
9
10

try {
if (DataManager.enemyFighters.fightObj.length >= 5) {
DataManager.turnOverButton.loadTexture("hero_turn_button");
alert("敌人选择不出牌,不知道有什么阴谋诡计");
return;
}
}catch(e){

}

然后补牌逻辑的判定和上述的方法类似,只要判断手牌是否大于8张,如果大于或者等于8张,那么抽到的卡牌就无法再加入到手牌之中,也就是会被摧毁掉。

打开HandCard.js类,在addCard方法中加上:

1
2
3
4
5

if (this.cardObjList.length >= 8) {
alert("你的手牌已达到上限,当前到的卡牌被销毁");
return;
}

EnemyHandCard因为重写了addCard方法,所以我们在EnemyHandCard中也这样做一次就可以了。

1
2
3
4
5

if (this.cardViewList.length >= 8) {
alert("敌人已达到上限,当前到的卡牌被销毁");
return;
}

这样,一些基本卡组的限制就做完了。别忘了,我们还剩一件事情没做。那就试关于游戏胜负的结算。

我们新建一个场景,ResultScene.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

/**
* 游戏结果场景
*/

var DataManager = require("../class/DataManager");

function ResultScene(game) {
this.create = function () {
if (DataManager.result == 0) {
console.log("敌人胜利");
var text = game.add.text(game.world.centerX, game.world.centerY, "You Loss", {
fill: "#000",
});

text.anchor.set(0.5);

} else {
var text = game.add.text(game.world.centerX, game.world.centerY, "You Win", {
fill: "#000",
fontSize: "30pt"
});
text.anchor.set(0.5);
}
}
}

module.exports = ResultScene;

然后在我们main.js中加入这个场景:

1
2
3
4
5
6
7
8
9
10
11

var StartScene = require("./modules/scenes/StartScene");
var GameScene = require("./modules/scenes/GameScene");
var ResultScene = require("./modules/scenes/ResultScene");

var game = new Phaser.Game(800, 600, Phaser.AUTO, 'mainCanvas', {}, true);

game.state.add("StartScene", StartScene); // 游戏开始场景
game.state.add("GameScene", GameScene); // 游戏场景
game.state.add("ResultScene", ResultScene); // 游戏结果场景
game.state.start("StartScene");

最后在结算中加入我们的触发函数,打开EnemnyHead.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

/**
* 敌人头像
*/

var utils = require("../Utils");
var Head = require("./Head");
var DataManager = require("./DataManager");

function EnemyHead(game, textureName, positionX, positionY) {
Head.apply(this, arguments);
}


// 设置敌人头像
// @override重写setPic
Head.prototype.setPic = function (game) {
var pic = game.add.image(0, 0, this.textureName);

pic.inputEnabled = true;
pic.events.onInputDown.add(function () {
if (DataManager.heroFighterChoise == null) {
return;
}

else {
var _hp = parseInt(this.HPObj.text) - DataManager.heroFighterChoise.attack;
this.HPObj.setText(_hp);

DataManager.heroFighterChoise.alpha = 0.7;
DataManager.heroFighterChoise.sleep = true;
DataManager.heroFighterChoise.children[2].alpha = 0;

alert("我方的" + DataManager.heroFighterChoise.cnName + "攻击了敌人英雄");

DataManager.heroFighterChoise = null;

if (parseInt(this.HPObj.text) <= 0) {
alert("玩家获取胜利,敌人阵亡");
DataManager.result = 1;
game.state.start("ResultScene");
}
}

}, this);

return pic;
}

utils.extend(EnemyHead, Head);

module.exports = EnemyHead;

AI.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

/**
* 电脑AI
*/
var DataManager = require("./DataManager");
var EnemyFighter = require("./EnemyFighter");

function AI() {
this.enemyChoise = null;
}

// 出牌
AI.prototype.shotCard = function (game) {
this.enemyChoise = this.choiseCard();
DataManager.turn = 0;

if (!this.enemyChoise) {
// 没有合适的卡牌
DataManager.turnOverButton.loadTexture("hero_turn_button");
alert("敌人选择不出牌,不知道有什么阴谋诡计");
return;
}

try {
if (DataManager.enemyFighters.fightObj.length >= 5) {
DataManager.turnOverButton.loadTexture("hero_turn_button");
alert("敌人选择不出牌,不知道有什么阴谋诡计");
return;
}
} catch (e) {

}

if (DataManager.enemyFighters == null) {
DataManager.enemyFighters = new EnemyFighter(game);
DataManager.enemyFighters.buildFighter(game, this.enemyChoise.cardInfo.HP, this.enemyChoise.cardInfo.attack, this.enemyChoise.cardInfo.cnName, this.enemyChoise.cardInfo.fight);
} else {
DataManager.enemyFighters.buildFighter(game, this.enemyChoise.cardInfo.HP, this.enemyChoise.cardInfo.attack, this.enemyChoise.cardInfo.cnName, this.enemyChoise.cardInfo.fight);
}

this.enemyChoise.destroy();
DataManager.enemyHandCard.reListHandCard();
this.enemyChoise = null;

DataManager.turnOverButton.loadTexture("hero_turn_button");
}

// 选择手牌
AI.prototype.choiseCard = function () {
var shotList = [];
var _fee = parseInt(DataManager.enemyFee.feeObj.text.split("/")[0]);

for (var i = 0; i < DataManager.enemyHandCard.cardViewList.length; i++) {
if (_fee >= DataManager.enemyHandCard.cardViewList[i].cardInfo.fee) {
// 只要费用允许,就放入可出的牌之中
shotList.push(DataManager.enemyHandCard.cardViewList[i]);
}
}

if (shotList.length >= 1) {
// 返回左手第一张牌
return shotList[0];
}
}

// 选择要攻击的目标
AI.prototype.choiseAttackTarget = function (game) {
// 敌人没有随从
if (!DataManager.enemyFighters || DataManager.enemyFighters.fightObj.length == 0) {
return;
}

if (DataManager.heroFighters == null) { // 判断玩家的随从是否存在
for (var i = 0; i < DataManager.enemyFighters.fightObj.length; i++) {
if (DataManager.enemyFighters.fightObj[i].sleep == false) {
alert("敌人的" + DataManager.enemyFighters.fightObj[i].cnName + "攻击了你的英雄");

// 更新攻击之后的状态
DataManager.enemyFighters.fightObj[i].sleep = true;
DataManager.enemyFighters.fightObj[i].alpha = 0.7;
DataManager.heroHead.HPObj.setText(parseInt(DataManager.heroHead.HPObj.text) - DataManager.enemyFighters.fightObj[i].attack);

if (parseInt(DataManager.heroHead.HPObj.text) <= 0) {
DataManager.result = 0;
alert("敌人获取了胜利,玩家阵亡");
game.state.start("ResultScene");
return;
}
}
}
} else {

var _heroFightersAttack = 0;
var _enemyFightersAttack = 0;

// 计算电脑AI的场攻
for (var j = 0; j < DataManager.enemyFighters.fightObj.length; j++) {
_enemyFightersAttack += DataManager.enemyFighters.fightObj[j].attack;
}

// 计算玩家战场上的场攻
for (var k = 0; k < DataManager.heroFighters.fightObj.length; k++) {
_heroFightersAttack += DataManager.heroFighters.fightObj[k].attack;
}

console.log(_heroFightersAttack, _enemyFightersAttack);

var _destroyList = [];
for (var i = 0; i < DataManager.enemyFighters.fightObj.length; i++) {
if (DataManager.enemyFighters.fightObj[i].sleep == false) {
console.log("attack");

if (_enemyFightersAttack >= _heroFightersAttack) { // AI场攻大于玩家随从的场攻
alert("敌人的" + DataManager.enemyFighters.fightObj[i].cnName + "攻击了你的英雄");
// 更新攻击之后的状态
DataManager.enemyFighters.fightObj[i].sleep = true;
DataManager.enemyFighters.fightObj[i].alpha = 0.7;
DataManager.heroHead.HPObj.setText(parseInt(DataManager.heroHead.HPObj.text) - DataManager.enemyFighters.fightObj[i].attack);

if (parseInt(DataManager.heroHead.HPObj.text) <= 0) {
DataManager.result = 0;
alert("敌人获取了胜利,玩家阵亡");
game.state.start("ResultScene");
return;
}

} else {
// AI场攻小于玩家场攻则攻击随从
alert("敌方的" + DataManager.enemyFighters.fightObj[i].cnName + "攻击了我方的" + DataManager.heroFighters.fightObj[0].cnName);

var _heroFightHP = DataManager.enemyFighters.fightObj[i].hp - DataManager.heroFighters.fightObj[0].attack;
var _enemyFightHP = DataManager.heroFighters.fightObj[0].hp - DataManager.enemyFighters.fightObj[i].attack;

// 更新玩家的随从的hp
DataManager.enemyFighters.fightObj[i].hp = _heroFightHP;
DataManager.enemyFighters.fightObj[i].alpha = 0.7;
DataManager.enemyFighters.fightObj[i].sleep = true;
DataManager.enemyFighters.fightObj[i].children[2].alpha = 0;
DataManager.enemyFighters.fightObj[i].children[1].setText(_heroFightHP);

// 更新敌人的玩家的hp
DataManager.heroFighters.fightObj[0].hp = _enemyFightHP;
DataManager.heroFighters.fightObj[0].children[1].setText(_enemyFightHP);

if (_heroFightHP <= 0) {
_destroyList.push(DataManager.enemyFighters.fightObj[i]);
}

if (_enemyFightHP <= 0) {
DataManager.heroFighters.fightObj[0].destroy();
DataManager.heroFighters.reListObjs();
}
}
}
}

for (var n = 0; n < _destroyList.length; n++) {
_destroyList[n].destroy();
}
DataManager.enemyFighters.reListObjs();
}
}


module.exports = AI;

到这里游戏的主要的逻辑都已经实现完毕了。现在完全可以和电脑来一局纯靠人品的战斗~

image

六.小结

教程在这里就告了一个段落了。但是不意味着游戏就已经结束了。还有一个简单的小功能,剩余卡牌的提示功能这里我就不再做了,如果对前面的文章有认真研究的同学,应该可以自己把这个功能开发出来。

关于拓展:

游戏的各个组件基本都是封装成了类的,游戏的主要逻辑是写在UIManager.js中的各个按钮事件的触发之中,所有驱动游戏进程的数据在DataManager.js中。如果你要把游戏拓展得比现有功能更加强大,更加完美那也是完全可行的。

最后,感谢各位的收看,整个项目托管地址在这里